package sudoku.model;

import java.awt.Color;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.Timer;

import sudoku.controller.Controller;
import sudoku.data.Button;
import sudoku.data.SudokuButton;
import sudoku.ui.SudokuUI;
import sudoku.util.Config;
import sudoku.util.Data;
import sudoku.util.Language;
import sudoku.util.game.GenerateNewGame;
import sudoku.util.game.SolveDLX;
import sudoku.util.game.Solver;

public class SudokuModel implements Runnable {
  private volatile Config config;
  private volatile Data data;
  private volatile ChoiceModel choiceModel;
  private volatile SudokuUI m2ng;
  private volatile Controller controller;
  private volatile Language language;
  public volatile GenerateNewGame generate;
  private volatile SudokuModel hindamismodel;
  private SudokuButton sudokuButtons[][];
  private int valik_i;
  private int valik_j;
  private int valitud_m2ng;
  private boolean onAlustatud;
  private boolean onPaus;
  private boolean abi;
  private boolean geneeritud;
  private String nimi;
  public volatile Thread thread;
  private LinkedList<Button> undo;
  private LinkedList<Button> redo;
  private boolean unre;
  private boolean un;
  private String lastLevel;
  private String level;
  private String genLevel;
  private int maxtehnika;
  private boolean hintUsed;
  private int difficulty;
  private volatile boolean generating;

  public SudokuModel(Language language, Config config, Data data) {
    generating = false;
    this.language = language;
    this.config = config;
    this.data = data;
    valitud_m2ng = -1;
    uusModel();
    geneeritud = false;
    level = "";
  }

  public void avaM2ng(String esialgne[], String lahendus[], String abi[]) {
    int mitmes = 0;
    if (esialgne.length > 81) {
      config.setRuudustik(16);
      config.setKast(4);
      m2ng.getRuudustik16().setSelected(true);
      m2ng.getRuudustik9().setSelected(false);
    } else {
      config.setRuudustik(9);
      config.setKast(3);
      m2ng.getRuudustik16().setSelected(false);
      m2ng.getRuudustik9().setSelected(true);
    }
    uusModel();
    for (int i = 0; i < sudokuButtons.length; i++) {
      for (int j = 0; j < sudokuButtons[i].length; j++) {
        if (esialgne[mitmes].equals(" ")) {
          if (lahendus[mitmes].equals(" ")) {
            sudokuButtons[i][j].setNumber(" ");
          } else {
            sudokuButtons[i][j].setNumber(lahendus[mitmes]);
            sudokuButtons[i][j].setV2rv(config.getLahendus());
          }
        } else {
          sudokuButtons[i][j].setNumber(esialgne[mitmes]);
          sudokuButtons[i][j].setV2rv(config.getEsialgne());
        }
        if (!abi[mitmes].equals(" ")) {
          sudokuButtons[i][j].setAbiNupp(true);
          String abid[] = abi[mitmes].split(",");
          for (int k = 0; k < abid.length; k++) {
            ((JLabel) m2ng.getRuut()[i][j].getComponent(k)).setText(abid[k]);
            m2ng.getRuut()[i][j].revalidate();
          }

        }
        mitmes++;
      }

    }

    m2ng.uuendaUI();
  }

  public void genereeriUuedM2ngud(JDialog d, Timer timer) {
    generate = new GenerateNewGame(this, config, language, 1);
    generate.setD(d);
    generate.setTimer(timer);
    generate.setController(controller);
    generate.setM2ng(m2ng);
    thread = new Thread(generate);
    thread.start();
  }

  public Config getConfig() {
    return config;
  }

  public Data getData() {
    return data;
  }

  public String getLahendus(boolean full) {
    String s = "";
    for (int i = 0; i < sudokuButtons.length; i++) {
      for (int j = 0; j < sudokuButtons[i].length; j++) {
        if (full) {
          s = s + sudokuButtons[i][j].getNumber() + ",";
        } else if (sudokuButtons[i][j].getV2rv().equals(config.getEsialgne())) {
          s = s + sudokuButtons[i][j].getNumber() + ",";
        } else {
          s = s + " ,";
        }
      }

    }

    return s.substring(0, s.length() - 1);
  }

  public Language getLanguage() {
    return language;
  }

  public String getLastLevel() {
    return lastLevel;
  }

  public String getLevel() {
    return level;
  }

  public String getNimi() {
    return nimi;
  }

  public LinkedList<Button> getRedo() {
    return redo;
  }

  public SudokuButton[][] getSudokuButtons() {
    return sudokuButtons;
  }

  public LinkedList<Button> getUndo() {
    return undo;
  }

  public int getValik_i() {
    return valik_i;
  }

  public int getValik_j() {
    return valik_j;
  }

  public int getValitud_m2ng() {
    return valitud_m2ng;
  }

  public boolean isAbi() {
    return abi;
  }

  public boolean isGenerating() {
    return generating;
  }

  public boolean isGenerationNeeded() {
    if (level.equals(language.getSudoku_diffeasy()) && config.getEasy().size() == 0) {
      return true;
    }
    if (level.equals(language.getSudoku_diffmedium()) && config.getMedium().size() == 0) {
      return true;
    }
    if (level.equals(language.getSudoku_diffhard()) && config.getHard().size() == 0) {
      return true;
    }
    if (level.equals(language.getSudoku_diffexpert()) && config.getExpert().size() == 0) {
      return true;
    }
    return level.equals(language.getSudoku_diffextreme()) && config.getExtreme().size() == 0;
  }

  public boolean isOnAlustatud() {
    return onAlustatud;
  }

  public boolean isOnPaus() {
    return onPaus;
  }

  public void kirjutaNumber() {
    if ((!config.isComputerchoice() || choiceModel.getChoiceButton()[choiceModel.getValik_i()].isKasEksisteerib())
        && (sudokuButtons[valik_i][valik_j].getV2rv().equals(config.getLahendus()) || !onAlustatud)) {
      if (!unre && !abi) {
        for (int i = redo.size(); i > undo.size(); i--) {
          redo.removeLast();
        }

      }
      if (!un && !abi) {
        undo.add(new Button(valik_i, valik_j, sudokuButtons[valik_i][valik_j].getNumber(), onAlustatud));
      }
      if (choiceModel.getValik_i() != 0) {
        if (abi) {
          sudokuButtons[valik_i][valik_j].setNumber(String.valueOf(choiceModel.getValik_i()));
          sudokuButtons[valik_i][valik_j].setAbiNupp(true);
        } else {
          sudokuButtons[valik_i][valik_j].setAbiNupp(false);
          sudokuButtons[valik_i][valik_j].setNumber(String.valueOf(choiceModel.getValik_i()));
          if (onAlustatud) {
            sudokuButtons[valik_i][valik_j].setV2rv(config.getLahendus());
          } else {
            sudokuButtons[valik_i][valik_j].setV2rv(config.getEsialgne());
          }
        }
      } else {
        sudokuButtons[valik_i][valik_j].setAbiNupp(false);
        sudokuButtons[valik_i][valik_j].setNumber(" ");
        sudokuButtons[valik_i][valik_j].setV2rv(config.getLahendus());
      }
      if (!unre && !abi) {
        redo.add(new Button(valik_i, valik_j, sudokuButtons[valik_i][valik_j].getNumber(), onAlustatud));
      }
    }
    if (unre) {
      unre = false;
      un = false;
    }
    m2ng.uuendaBackground(valik_i, valik_j);
  }

  public void lahendaM2ng() {
    if (veaKontroll()) {
      hintUsed = true;
      SolveDLX sdlx = new SolveDLX(config.getRuudustik());
      sdlx.BuildSudokuMatrix();
      sdlx.SetStartingGrid(getLahendus(true));
      sdlx.SolveSudoku(4);
      if (sdlx.unique) {
        setLahendus(sdlx.getSolution(), true);
        m2ng.uuendaUI();
        m2ng.uuendaBackgroundAll();
        controller.handleEvent("v6it");
      } else {
        JOptionPane.showMessageDialog(m2ng, language.getDLX_solveerror(sdlx.getSolutionsFound()));
      }
    } else {
      JOptionPane.showMessageDialog(m2ng, language.getModel_hinterror(), language.getMessage(), 1);
    }
  }

  public synchronized void lahendaHindaM2ng() {
    String s = "";
    for (int i = 0; i < sudokuButtons.length; i++) {
      for (int j = 0; j < sudokuButtons[i].length; j++) {
        if (sudokuButtons[i][j].getV2rv().equals(config.getEsialgne())) {
          s = s + "," + sudokuButtons[i][j].getNumber();
        } else {
          s = s + ", ";
        }
      }

    }

    lahendaHindaM2ng(s.substring(1));
    setLevel(genLevel);
    m2ng.setLevel();
  }

  public synchronized void lahendaHindaM2ng(String s) {
    maxtehnika = 0;
    hindamismodel = new SudokuModel(language, config, data);
    hindamismodel.setGenerating(generating);
    String ss[] = s.split(",");
    int nr = 0;
    for (int i = 0; i < hindamismodel.getSudokuButtons().length; i++) {
      for (int j = 0; j < hindamismodel.getSudokuButtons()[i].length; j++) {
        if (!ss[nr].equals(" ")) {
          hindamismodel.getSudokuButtons()[i][j].setNumber(ss[nr]);
          hindamismodel.getSudokuButtons()[i][j].setV2rv(config.getEsialgne());
        }
        nr++;
      }

    }

    difficulty = 0;
    while (lahendaSamm()) {
      ;
    }
    if (!hindamismodel.v6iduKontroll()) {
      maxtehnika = 999;
    }
    if (config.isDebug() && !generating) {
      Config.log("model max " + maxtehnika);
      Config.log("diff: " + difficulty);
      Config.log("counter: " + loenda());
    }
    double border = 1.0D;
    if (config.getRuudustik() == 9) {
      border = 1.0D;
    } else {
      border = 0.33329999999999999D;
    }
    if (maxtehnika < 3 && difficulty < 270D / border) {
      genLevel = language.getSudoku_diffeasy();
    } else if (maxtehnika < 9 && difficulty < 270D / border + 400D) {
      genLevel = language.getSudoku_diffmedium();
    } else if (maxtehnika < 999 && (maxtehnika < 9 || difficulty < 500D / border + 1000D)) {
      genLevel = language.getSudoku_diffhard();
    } else if (maxtehnika < 999) {
      genLevel = language.getSudoku_diffexpert();
    } else {
      genLevel = language.getSudoku_diffextreme();
    }
  }

  public boolean lahendaCheat() {
    m2ng.uuendaBackgroundAll();
    Solver solver = new Solver(config, this, config.getRuudustik(), config.getKast());
    SudokuButton button = solver.solve();
    if (button == null) {
      return false;
    }
    m2ng.solver(button.getI(), button.getJ());
    for (int m = redo.size(); m > undo.size(); m--) {
      redo.removeLast();
    }

    undo.add(new Button(button.getI(), button.getJ(), sudokuButtons[button.getI()][button.getJ()].getNumber(), onAlustatud));
    sudokuButtons[button.getI()][button.getJ()].setNumber(String.valueOf(button.getKandidaat() + 1));
    sudokuButtons[button.getI()][button.getJ()].setV2rv(config.getLahendus());
    redo.add(new Button(button.getI(), button.getJ(), sudokuButtons[button.getI()][button.getJ()].getNumber(), onAlustatud));
    m2ng.uuendaUI();
    controller.handleEvent("v6it");
    return true;
  }

  public boolean lahendaHint() {
    if (veaKontroll()) {
      String s = getLahendus(true);
      SolveDLX sdlx = new SolveDLX(config.getRuudustik());
      sdlx.BuildSudokuMatrix();
      sdlx.SetStartingGrid(s);
      sdlx.SolveSudoku(1);
      if (sdlx.gotSolution) {
        m2ng.resetColorers();
        Solver solver = new Solver(config, this, config.getRuudustik(), config.getKast());
        SudokuButton button = solver.solve();
        if (button == null) {
          m2ng.status(language.getSudoku_statusnomorehints(), new Color(255, 255, 102));
          return false;
        } else {
          m2ng.solver(button.getI(), button.getJ());
          return true;
        }
      } else {
        JOptionPane.showMessageDialog(m2ng, language.getModel_hinterror(), language.getMessage(), 1);
        return false;
      }
    } else {
      JOptionPane.showMessageDialog(m2ng, language.getModel_hinterror(), language.getMessage(), 1);
      return false;
    }
  }

  public boolean lahendaSamm() {
    Solver solver = new Solver(config, hindamismodel, config.getRuudustik(), config.getKast());
    solver.setMaxtehnika(0);
    SudokuButton button = solver.level17(null);
    if (button == null) {
      return false;
    }
    difficulty += solver.getDifficulty();
    if (maxtehnika < solver.getMaxtehnika()) {
      maxtehnika = solver.getMaxtehnika();
    }
    hindamismodel.getSudokuButtons()[button.getI()][button.getJ()].setNumber(String.valueOf(button.getKandidaat() + 1));
    hindamismodel.getSudokuButtons()[button.getI()][button.getJ()].setV2rv(config.getLahendus());
    return true;
  }

  private int loenda() {
    int arv = 0;
    for (int i = 0; i < sudokuButtons.length; i++) {
      for (int j = 0; j < sudokuButtons[i].length; j++) {
        if (sudokuButtons[i][j].getV2rv().equals(config.getEsialgne())) {
          arv++;
        }
      }

    }

    return arv;
  }

  public void redo() {
    unre = true;
    abi = false;
    Button button = redo.get(undo.size());
    valik_i = button.getI();
    valik_j = button.getJ();
    try {
      choiceModel.setValik_i(Integer.parseInt(button.getNumber()));
    } catch (Exception e) {
      choiceModel.setValik_i(0);
    }
    choiceModel.uusModel();
    controller.handleEvent("kirjutaNumber");
  }

  public void setAbi(boolean abi) {
    this.abi = abi;
  }

  public void setChoiceModel(ChoiceModel choiceModel) {
    this.choiceModel = choiceModel;
  }

  public void setController(Controller controller) {
    this.controller = controller;
  }

  public void setGenerating(boolean generating) {
    this.generating = generating;
  }

  public void setGeneeritud(boolean geneeritud) {
    this.geneeritud = geneeritud;
  }

  public void setLahendus(String s) {
    String ss[] = s.split(",");
    int nr = 0;
    for (int i = 0; i < sudokuButtons.length; i++) {
      for (int j = 0; j < sudokuButtons[i].length; j++) {
        if (!ss[nr].equals(" ")) {
          sudokuButtons[i][j].setNumber(ss[nr]);
          sudokuButtons[i][j].setV2rv(config.getEsialgne());
        }
        nr++;
      }

    }

  }

  public void setLahendus(String s, boolean full) {
    String ss[] = s.split(",");
    int nr = 0;
    for (int i = 0; i < sudokuButtons.length; i++) {
      for (int j = 0; j < sudokuButtons[i].length; j++) {
        if (sudokuButtons[i][j].getNumber().equals(" ")) {
          sudokuButtons[i][j].setNumber(ss[nr]);
          sudokuButtons[i][j].setV2rv(config.getLahendus());
        }
        nr++;
      }

    }

  }

  public void setLastLevel(String lastLevel) {
    this.lastLevel = lastLevel;
  }

  public void setLevel(String level) {
    this.level = level;
  }

  public void setM2ng(SudokuUI m2ng) {
    this.m2ng = m2ng;
  }

  public void setNimi(String nimi) {
    this.nimi = nimi;
  }

  public void setOnAlustatud(boolean onAlustatud) {
    this.onAlustatud = onAlustatud;
  }

  public void setOnPaus(boolean onPaus) {
    this.onPaus = onPaus;
  }

  public void setValik_i(int valik_i) {
    this.valik_i = valik_i;
  }

  public void setValik_j(int valik_j) {
    this.valik_j = valik_j;
  }

  public void setValitud_m2ng(int valitud_m2ng) {
    this.valitud_m2ng = valitud_m2ng;
  }

  public void undo() {
    unre = true;
    un = true;
    abi = false;
    Button button = undo.removeLast();
    valik_i = button.getI();
    valik_j = button.getJ();
    try {
      choiceModel.setValik_i(Integer.parseInt(button.getNumber()));
    } catch (Exception e) {
      choiceModel.setValik_i(0);
    }
    choiceModel.uusModel();
    controller.handleEvent("kirjutaNumber");
  }

  public void uuendaModel(Color lah, Color esi) {
    for (int i = 0; i < config.getRuudustik(); i++) {
      for (int j = 0; j < config.getRuudustik(); j++) {
        if (sudokuButtons[i][j].getV2rv().equals(lah)) {
          sudokuButtons[i][j].setV2rv(config.getLahendus());
        }
        if (sudokuButtons[i][j].getV2rv().equals(esi)) {
          sudokuButtons[i][j].setV2rv(config.getEsialgne());
        }
      }

    }

  }

  public void uuesti() {
    undo = new LinkedList<Button>();
    redo = new LinkedList<Button>();
    for (int i = 0; i < config.getRuudustik(); i++) {
      for (int j = 0; j < config.getRuudustik(); j++) {
        if (sudokuButtons[i][j].getV2rv().equals(config.getLahendus()) || sudokuButtons[i][j].getV2rv().equals(config.getHelpButton())) {
          sudokuButtons[i][j].setNumber(" ");
        }
      }

    }

    onAlustatud = false;
  }

  public void uusModel() {
    hintUsed = false;
    sudokuButtons = new SudokuButton[config.getRuudustik()][config.getRuudustik()];
    undo = new LinkedList<Button>();
    redo = new LinkedList<Button>();
    unre = false;
    un = false;
    for (int i = 0; i < config.getRuudustik(); i++) {
      for (int j = 0; j < config.getRuudustik(); j++) {
        sudokuButtons[i][j] = new SudokuButton(config.getLahendus(), i, j);
        if (geneeritud) {
          sudokuButtons[i][j].setV2rv(config.getEsialgne());
        }
      }

    }

  }

  public void valitudM2ng() {
    nimi = String.valueOf(valitud_m2ng + 1);
    String sisu[] = (data.getOlemasolevadM2ngud(data.getGames()).get(valitud_m2ng)).split(",");
    if (sisu.length > 81) {
      config.setRuudustik(16);
      config.setKast(4);
      m2ng.getRuudustik16().setSelected(true);
      m2ng.getRuudustik9().setSelected(false);
    } else {
      config.setRuudustik(9);
      config.setKast(3);
      m2ng.getRuudustik16().setSelected(false);
      m2ng.getRuudustik9().setSelected(true);
    }
    uusModel();
    int mitmes = 0;
    for (int i = 0; i < sudokuButtons.length; i++) {
      for (int j = 0; j < sudokuButtons[i].length; j++) {
        if (!sisu[mitmes].equals(" ")) {
          sudokuButtons[i][j].setNumber(sisu[mitmes]);
          sudokuButtons[i][j].setV2rv(config.getEsialgne());
        }
        mitmes++;
      }

    }

  }

  public boolean veaKontroll() {
    for (int i = 0; i < config.getRuudustik(); i++) {
      for (int j = 0; j < config.getRuudustik(); j++) {
        String nupuTekst = sudokuButtons[i][j].getNumber();
        if (!nupuTekst.equals(" ")) {
          for (int k = 0; k < config.getRuudustik(); k++) {
            if (nupuTekst.equals(sudokuButtons[i][k].getNumber()) && k != j) {
              return false;
            }
            if (nupuTekst.equals(sudokuButtons[k][j].getNumber()) && k != i) {
              return false;
            }
          }

          int rida = 0;
          int veerg = 0;
          if (i > config.getKast() - 1 && i < config.getKast() * 2) {
            rida = config.getKast();
          }
          if (i > config.getKast() * 2 - 1 && i < config.getKast() * 3) {
            rida = config.getKast() * 2;
          }
          if (i > config.getKast() * 3 - 1 && i < config.getKast() * 4) {
            rida = config.getKast() * 3;
          }
          if (j > config.getKast() - 1 && j < config.getKast() * 2) {
            veerg = config.getKast();
          }
          if (j > config.getKast() * 2 - 1 && j < config.getKast() * 3) {
            veerg = config.getKast() * 2;
          }
          if (j > config.getKast() * 3 - 1 && j < config.getKast() * 4) {
            veerg = config.getKast() * 3;
          }
          for (int k = rida; k < rida + config.getKast(); k++) {
            for (int l = veerg; l < veerg + config.getKast(); l++) {
              if (nupuTekst.equals(sudokuButtons[k][l].getNumber()) && k != i && l != j) {
                return false;
              }
            }

          }

        }
      }

    }

    return true;
  }

  public boolean v6iduKontroll() {
    for (int i = 0; i < config.getRuudustik(); i++) {
      for (int j = 0; j < config.getRuudustik(); j++) {
        if (sudokuButtons[i][j].getNumber().equals(" ")) {
          return false;
        }
      }

    }

    return veaKontroll();
  }

  public void v6it() {
    boolean onOlemas = false;
    onAlustatud = false;
    undo = new LinkedList<Button>();
    redo = new LinkedList<Button>();
    if (!hintUsed) {
      int r = config.getRuudustik();
      if (valitud_m2ng != -1) {
        List<String> m2nguAjad = data.getM2nguAjad();
        for (int i = 0; i < m2nguAjad.size(); i++) {
          String s[] = (m2nguAjad.get(i)).split("\t");
          if (s[2].equals(r + "x" + r) && s[0].equals(String.valueOf(valitud_m2ng + 1))) {
            onOlemas = true;
            if (s[1].compareTo(m2ng.getTime_label().getText()) >= 0) {
              m2nguAjad.remove(i);
              m2nguAjad.add(i, String.valueOf(valitud_m2ng + 1) + "\t" + m2ng.getTime_label().getText() + "\t" + r + "x" + r);
            }
          }
        }

        if (!onOlemas) {
          m2nguAjad.add(String.valueOf(valitud_m2ng + 1) + "\t" + m2ng.getTime_label().getText() + "\t" + r + "x" + r);
        }
        data.setM2nguAjad(m2nguAjad);
      }
      List<String> ajalugu = data.getAjalugu();
      onOlemas = false;
      for (int i = 0; i < ajalugu.size(); i++) {
        if (valitud_m2ng != -1) {
          String s[] = (ajalugu.get(i)).split("          ");
          if (s[0].equals(r + "x" + r + "   " + (valitud_m2ng + 1) + ". Sudoku")) {
            onOlemas = true;
            if (s[2].compareTo(m2ng.getTime_label().getText()) >= 0) {
              ajalugu.remove(i);
              ajalugu.add(i, r + "x" + r + "   " + String.valueOf(valitud_m2ng + 1) + ". Sudoku          " + config.getNimi()
                  + "          " + m2ng.getTime_label().getText());
            }
          }
        } else {
          String s[] = (ajalugu.get(i)).split("          ");
          if (s[0].equals(r + "x" + r + "   " + nimi)) {
            onOlemas = true;
            if (s[2].compareTo(m2ng.getTime_label().getText()) >= 0) {
              ajalugu.remove(i);
              ajalugu.add(i, r + "x" + r + "   " + nimi + "          " + config.getNimi() + "          " + m2ng.getTime_label().getText());
            }
          }
        }
      }

      if (!onOlemas) {
        if (valitud_m2ng != -1) {
          ajalugu.add(r + "x" + r + "   " + String.valueOf(valitud_m2ng + 1) + ". Sudoku          " + config.getNimi() + "          "
              + m2ng.getTime_label().getText());
        } else {
          ajalugu.add(String.valueOf(r + "x" + r + "   " + nimi + "          " + config.getNimi() + "          "
              + m2ng.getTime_label().getText()));
        }
      }
      data.setAjalugu(ajalugu);
    }
    m2ng.seadistaLahendajad();
  }

  public String getGenLevel() {
    return genLevel;
  }

  public void setGenLevel(String genLevel) {
    this.genLevel = genLevel;
  }

  @Override
  public void run() {
    lahendaHindaM2ng();
  }

}